﻿using Common;
using InTheHand.Net;
using InTheHand.Net.Bluetooth;
using InTheHand.Net.Sockets;
using MuseLibrary.Events;
using MuseLibrary.Packets;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ConnectionEventHandler = MuseLibrary.Events.Delegates.ConnectionEventHandler;
using PacketEventHandler = MuseLibrary.Events.Delegates.PacketEventHandler;
using Timer = System.Timers.Timer;

namespace MuseLibrary
{
    public class MuseConnection
    {
        public event ConnectionEventHandler ConnectionStatusUpdated;
        public event PacketEventHandler PacketReceived;
        private int _notchFrequency = 60;
        public MuseConnection(String address, bool is50HzNotchFrequency)
        {
            _address = BluetoothAddress.Parse(address);
            _encoding = Encoding.ASCII;
            _client = new BluetoothClient();
            _client.SetPin("1234");
            _channelValues = new int[4];
            InitialiseMessages();
            if (is50HzNotchFrequency)
                _notchFrequency = 50;

            _keepAliveTimer = new Timer() { Interval = 8000, Enabled = false };
            _keepAliveTimer.Elapsed += (s, e) => SendKeepAliveMessage();
        }

        public void Connect()
        {
            if (ConnectionStatusUpdated != null)
                ConnectionStatusUpdated(this, new ConnectionEventArgs(_client, ConnectionStatus.Connecting));

            _connectionCallback = new AsyncCallback(Callback);
            _client.BeginConnect(_address, _serialPort, _connectionCallback, new object());
        }

        private void InitialiseMessages()
        {
            _museMessages.Add(MessageType.KeepAlive, "k");
            _museMessages.Add(MessageType.KillStreaming, "h");
            _museMessages.Add(MessageType.StartStreaming, "s");
            _museMessages.Add(MessageType.VersionInformation, "v");
            _museMessages.Add(MessageType.Preset10, "% 10");
            _museMessages.Add(MessageType.Preset14, "% 14");
            _museMessages.Add(MessageType.NotchFrequency50Hz, "g 407C");
            _museMessages.Add(MessageType.NotchFrequency60Hz, "g 408C");
        }

        private Guid _serialPort = BluetoothService.SerialPort;
        private BluetoothAddress _address;
        private AsyncCallback _connectionCallback;
        private int[] _channelValues;
        private bool _isConnected = false;

        public bool IsConnected
        {
            get { return _isConnected; }
        }

        private void SendKeepAliveMessage()
        {
            SendMessage(MessageType.KeepAlive);
        }

        public void SendMessage(MessageType message, int? delay = null)
        {
            if (!delay.HasValue)
            {
                _writer.Write(String.Format("{0}\r\n", _museMessages[message]));
                _writer.Flush();
            }
            else
            {
                var delayTimer = new Timer() { Enabled = true, Interval = delay.Value };
                delayTimer.Elapsed += (s, e) =>
                    {
                        delayTimer.Enabled = false;
                        SendMessage(message);
                        delayTimer.Dispose();
                    };
            }
        }

        private void ReceivePersist(object state)
        {
            var reader = new BinaryReader(_networkStream);
            var buffer = new byte[500];

            while (true)
            {
                var numRead = reader.Read(buffer, 0, buffer.Length);
                if (numRead == 0)
                    return;

                for (int i = 0; i < numRead; i++)
                {
                    _byteQueue.Enqueue(buffer[i]);
                }

                while (_byteQueue.Count > 100)
                {
                    lock (_lockKillReceiveThread)
                        if (_killReceiveThread) return;

                    var b = _byteQueue.Dequeue();
                    var packet = PacketFactory.MakePacket(b);
                    if (packet == null) continue;

                    packet.ReadByte(b);
                    while (!packet.IsInitialised)
                    {
                        b = _byteQueue.Dequeue();
                        packet.ReadByte(b);
                    }

                    _packets.Add(packet);
                    if (packet.HasPostInitialisation)
                        packet.PostInitialisation(_channelValues);

                    if (PacketReceived != null)
                        OneLineThread.Start(() => PacketReceived(this, new PacketEventArgs(_client, packet)));
                }
            }
        }

        private void Callback(IAsyncResult result)
        {
            ConnectionStatus status;
            try
            {
                _networkStream = _client.GetStream();
                _writer = new StreamWriter(_networkStream, _encoding);
                _dataStreamThread = OneLineThread.Start(ReceivePersist);
                _keepAliveTimer.Enabled = true;
                
                SendMessage(MessageType.Preset14, 200);
                if (_notchFrequency != 60)
                    SendMessage(MessageType.NotchFrequency50Hz, 400);

                SendMessage(MessageType.StartStreaming, 600);
                _isConnected = true;
                status = ConnectionStatus.Connected;
            }
            catch
            {
                _networkStream = null;
                _writer = null;
                _keepAliveTimer.Enabled = false;
                status = ConnectionStatus.ConnectionFailed;
            }

            if (ConnectionStatusUpdated != null)
                ConnectionStatusUpdated(this, new ConnectionEventArgs(_client, status));

            if (status == ConnectionStatus.ConnectionFailed)
            {
                var reconnectTimer = new Timer() { Enabled = true, Interval = 1000 };
                reconnectTimer.Elapsed += (s, e) =>
                {
                    reconnectTimer.Enabled = false;
                    Connect();
                };
            }
        }

        private Thread _dataStreamThread;
        private StreamWriter _writer;
        private NetworkStream _networkStream;
        private BluetoothClient _client;
        private Encoding _encoding;
        private Timer _keepAliveTimer;
        private Queue<byte> _byteQueue = new Queue<byte>();
        private List<Packet> _packets = new List<Packet>();
        private Dictionary<MessageType, String> _museMessages = new Dictionary<MessageType, string>();
        private object _lockKillReceiveThread = new object();
        private bool _killReceiveThread = false;

        public int NotchFrequency
        {
            get { return _notchFrequency; }
            set { _notchFrequency = value; }
        }

        public void Disconnect()
        {
            if (_writer != null)
                SendMessage(MessageType.KillStreaming);

            lock (_lockKillReceiveThread)
                _killReceiveThread = true;
        }
    }
}
